16 庖丁解牛:kubelet

整体概览

+--------------------------------------------------------+

| +---------------------+        +---------------------+ |       

| |      kubelet        |        |     kube-proxy      | |       

| |                     |        |                     | |       

| +---------------------+        +---------------------+ |       

| +----------------------------------------------------+ |       

| | Container Runtime (Docker)                         | |       

| | +---------------------+    +---------------------+ | |       

| | |Pod                  |    |Pod                  | | |       

| | | +-----+ +-----+     |    |+-----++-----++-----+| | |       

| | | |C1   | |C2   |     |    ||C1   ||C2   ||C3   || | |       

| | | |     | |     |     |    ||     ||     ||     || | |       

| | | +-----+ +-----+     |    |+-----++-----++-----+| | |       

| | +---------------------+    +---------------------+ | |       

| +----------------------------------------------------+ |       

+--------------------------------------------------------+  

在第 3 节《宏观认识:整体架构》 中,我们知道了 K8S 中 Node 由一些必要的组件构成,而其中最为核心的当属 kubelet 了,如果没有 kubelet 的存在,那我们预期的各类资源就只能存在于 Master 的相关组件中了,而 K8S 也很能只是一个 CRUD 的普通程序了。本节,我们来介绍下 kubelet 及它是如何完成这一系列任务的。

kubelet 是什么

按照一般架构设计上的习惯,kubelet 所承担的角色一般会被叫做 agent,这里叫做 kubelet 很大程度上受 Borg 的命名影响,Borg 里面也有一个 Borglet 的组件存在。kubelet 便是 K8S 中的 agent,负责 NodePod 相关的管理任务。

同样的,在我们下载 K8S 二进制文件解压后,便可以得到 kubelet 的可执行文件。在第 5 节中,我们也完成了 kubeletsystemd 进行启动和管理的相关配置。

kubelet 有什么作用

通常来讲 agent 这样的角色起到的作用首先便是要能够注册,让 server 端知道它的存在,所以这便是它的第一个作用:节点管理。

节点管理

当我们执行 kubelet --help 的时候,会看到它所支持的可配置参数,其中有一个 --register-node 参数便是用于控制是否向 kube-apiserver 注册节点的,默认是开启的。

我们在第 5 节中还介绍了如何新增一个 Node,当 kubeadm join 执行成功后,你便可以通过 kubectl get node 查看到新加入集群中的 Node,与此同时,你也可以在该节点上通过以下命令查看 kubelet 的状态。

master $ systemctl status kubelet

● kubelet.service - kubelet: The Kubernetes Agent

   Loaded: loaded (/etc/systemd/system/kubelet.service; enabled; vendor preset: disabled)

  Drop-In: /etc/systemd/system/kubelet.service.d

           └─kubeadm.conf

   Active: active (running) since Thu 2018-12-13 07:49:51 UTC; 32min ago

     Docs: http://kubernetes.io/docs/

 Main PID: 3876259 (kubelet)

   Memory: 66.3M

   CGroup: /system.slice/kubelet.service

           └─3876259 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernete...

当我们查看 Node 信息时,也能得到如下输出:

master $ kubectl get nodes node01 -o yaml

apiVersion: v1

kind: Node

metadata:

  annotations:

    kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock

    node.alpha.kubernetes.io/ttl: "0"

    volumes.kubernetes.io/controller-managed-attach-detach: "true"

  creationTimestamp: 2018-12-13T07:50:47Z

  labels:

    beta.kubernetes.io/arch: amd64

    beta.kubernetes.io/os: linux

    kubernetes.io/hostname: node01

  name: node01

  resourceVersion: "4242"

  selfLink: /api/v1/nodes/node01

  uid: cd612df6-feab-11e8-9a0b-0242ac110096

spec: {}

status:

  addresses:

  - address: 172.17.0.152

    type: InternalIP

  - address: node01

    type: Hostname

  allocatable:

    cpu: "4"

    ephemeral-storage: "89032026784"

    hugepages-1Gi: "0"

    hugepages-2Mi: "0"

    memory: 3894788Ki

    pods: "110"

  capacity:

    cpu: "4"

    ephemeral-storage: 96605932Ki

    hugepages-1Gi: "0"

    hugepages-2Mi: "0"

    memory: 3997188Ki

    pods: "110"

  conditions:

  - lastHeartbeatTime: 2018-12-13T08:39:41Z

    lastTransitionTime: 2018-12-13T07:50:47Z

    message: kubelet has sufficient disk space available

    reason: KubeletHasSufficientDisk

    status: "False"

    type: OutOfDisk

  - lastHeartbeatTime: 2018-12-13T08:39:41Z

    lastTransitionTime: 2018-12-13T07:50:47Z

    message: kubelet has sufficient memory available

    reason: KubeletHasSufficientMemory

    status: "False"

    type: MemoryPressure

  - lastHeartbeatTime: 2018-12-13T08:39:41Z

    lastTransitionTime: 2018-12-13T07:50:47Z

    message: kubelet has no disk pressure

    reason: KubeletHasNoDiskPressure

    status: "False"

    type: DiskPressure

  - lastHeartbeatTime: 2018-12-13T08:39:41Z

    lastTransitionTime: 2018-12-13T07:50:47Z

    message: kubelet has sufficient PID available

    reason: KubeletHasSufficientPID

    status: "False"

    type: PIDPressure

  - lastHeartbeatTime: 2018-12-13T08:39:41Z

    lastTransitionTime: 2018-12-13T07:51:37Z

    message: kubelet is posting ready status. AppArmor enabled

    reason: KubeletReady

    status: "True"

    type: Ready

  daemonEndpoints:

    kubeletEndpoint:

      Port: 10250

  images:

  - names:

    - k8s.gcr.io/[email protected]:956bea8c139620c9fc823fb81ff9b5647582b53bd33904302987d56ab24fc187

    - k8s.gcr.io/kube-apiserver-amd64:v1.11.3

    sizeBytes: 186676561

  nodeInfo:

    architecture: amd64

    bootID: 89ced22c-f7f8-4c4d-ad0d-d10887ab900e

    containerRuntimeVersion: docker://18.6.0

    kernelVersion: 4.4.0-62-generic

    kubeProxyVersion: v1.11.3

    kubeletVersion: v1.11.3

    machineID: 26ba042302eea8095d6576975c120eeb

    operatingSystem: linux

    osImage: Ubuntu 16.04.2 LTS

    systemUUID: 26ba042302eea8095d6576975c120eeb

可以看到 kubelet 不仅将自己注册给了 kube-apiserver,同时它所在机器的信息也都进行了上报,包括 CPU,内存,IP 信息等。

这其中有我们在第 2 节中提到的关于 Node 状态相关的一些信息,可以对照着看看。

当然这里除了这些信息外,还有些值得注意的,比如 daemonEndpoints 之类的,可以看到目前 kubelet 监听在了 10250 端口,这个端口可通过 --port 配置,但是之后会被废弃掉,我们是写入了 /var/lib/kubelet/config.yaml 的配置文件中。

master $ cat /var/lib/kubelet/config.yaml

address: 0.0.0.0

apiVersion: kubelet.config.k8s.io/v1beta1

authentication:

  anonymous:

    enabled: false

  webhook:

    cacheTTL: 2m0s

    enabled: true

  x509:

    clientCAFile: /etc/kubernetes/pki/ca.crt

authorization:

  mode: Webhook

  webhook:

    cacheAuthorizedTTL: 5m0s

    cacheUnauthorizedTTL: 30s

cgroupDriver: cgroupfs

cgroupsPerQOS: true

clusterDNS:

- 10.96.0.10

clusterDomain: cluster.local

containerLogMaxFiles: 5

containerLogMaxSize: 10Mi

contentType: application/vnd.kubernetes.protobuf

cpuCFSQuota: true

cpuManagerPolicy: none

cpuManagerReconcilePeriod: 10s

enableControllerAttachDetach: true

enableDebuggingHandlers: true

enforceNodeAllocatable:

- pods

eventBurst: 10

eventRecordQPS: 5

evictionHard:

  imagefs.available: 15%

  memory.available: 100Mi

  nodefs.available: 10%

  nodefs.inodesFree: 5%

evictionPressureTransitionPeriod: 5m0s

failSwapOn: true

fileCheckFrequency: 20s

hairpinMode: promiscuous-bridge

healthzBindAddress: 127.0.0.1

healthzPort: 10248

httpCheckFrequency: 20s

imageGCHighThresholdPercent: 85

imageGCLowThresholdPercent: 80

imageMinimumGCAge: 2m0s

iptablesDropBit: 15

iptablesMasqueradeBit: 14

kind: KubeletConfiguration

kubeAPIBurst: 10

kubeAPIQPS: 5

makeIPTablesUtilChains: true

maxOpenFiles: 1000000

maxPods: 110

nodeStatusUpdateFrequency: 10s

oomScoreAdj: -999

podPidsLimit: -1

port: 10250

registryBurst: 10

registryPullQPS: 5

resolvConf: /etc/resolv.conf

rotateCertificates: true

runtimeRequestTimeout: 2m0s

serializeImagePulls: true

staticPodPath: /etc/kubernetes/manifests

streamingConnectionIdleTimeout: 4h0m0s

syncFrequency: 1m0s

volumeStatsAggPeriod: 1m0s

master $

这其中有一些需要关注的配置:

  • maxPods:最大的 Pod

  • healthzBindAddresshealthzPort:配置了健康检查所监听的地址和端口

    我们可用以下方式进行验证:

    master $ curl 127.0.0.1:10248/healthz
    
    ok 
    
    
  • authenticationauthorization :认证授权相关

  • evictionHard:涉及到 kubelet 的驱逐策略,对 Pod 调度分配之类的影响很大

其余部分,可参考手册内容

Pod 管理

从上面的配置以及我们之前的介绍中,kube-scheduler 处理了 Pod 应该调度至哪个 Node,而 kubelet 则是保障该 Pod 能按照预期,在对应 Node 上启动并保持工作。

同时,kubelet 在保障 Pod 能按预期工作,主要是做了两方面的事情:

  • 健康检查:通过 LivenessProbeReadinessProbe 探针进行检查,判断是否健康及是否已经准备好接受请求。
  • 资源监控:通过 cAdvisor 进行资源监。

kubelet 是如何工作的

大致的功能已经介绍了,我们来看下它大体的实现。

首先是在 cmd/kubelet/app/server.go 文件中的 Run 方法:

func Run(s *options.KubeletServer, kubeDeps *kubelet.Dependencies, stopCh <-chan struct{}) error {

	glog.Infof("Version: %+v", version.Get())

	if err := initForOS(s.KubeletFlags.WindowsService); err != nil {

		return fmt.Errorf("failed OS init: %v", err)

	}

	if err := run(s, kubeDeps, stopCh); err != nil {

		return fmt.Errorf("failed to run Kubelet: %v", err)

	}

	return nil

}

这个方法看起来很简单那,它是在读取完一系列的配置和校验之后开始被调用的,在调用过程中,会在日志中输出当前的版本号,如果你的 kubelet 已经正常运行,当你执行 journalctl -u kubelet 的时候,便会看到一条相关的日志输出。

之后,便是一个 run 方法,其中包含着各种环境检查,容器管理,cAdvisor 初始化之类的操作,直到 kubelet 基本正确运行后,则会调用 pkg/kubelet/kubelet.go 中的一个 BirthCry 方法,该方法从命名就可以看出来,它其实就是宣告 kubelet 已经启动:

func (kl *Kubelet) BirthCry() {

	kl.recorder.Eventf(kl.nodeRef, v1.EventTypeNormal, events.StartingKubelet, "Starting kubelet.")

}

后续则是关于注册,Pod 管理以及资源相关的处理逻辑,内容较多,这里就不再展开了。

总结

本节中我们介绍了 kubelet 的主要功能和基本实现,了解到了它不仅可将自身注册到集群,同时还承担着保障 Pod 可在该 Node 上按预期工作。另外 kubelet 其实还承担着清理 Node 上一些由 K8S 调度 Pod 所造成的磁盘占用之类的工作。

从上面的配置中基本能看出来一些,这部分的功能大多数情况下不需要大家人为干预,所以也就不再展开了。

PodNode 上正常运行之后,若是需要对外提供服务,则需要将其暴露出来。下节,我们来介绍下 kube-proxy 是如何来处理这些工作的。